Skip to content

docs: add ADR for institution-defined tag code field#637

Merged
bradenmacdonald merged 5 commits into
openedx:mainfrom
mgwozdz-unicon:cbe_tag_code_field_adr
Jul 8, 2026
Merged

docs: add ADR for institution-defined tag code field#637
bradenmacdonald merged 5 commits into
openedx:mainfrom
mgwozdz-unicon:cbe_tag_code_field_adr

Conversation

@mgwozdz-unicon

Copy link
Copy Markdown
Contributor

Claude assisted...

Summary

  • Proposes Tag.code, a generic, optional field on openedx_tagging's Tag model, to hold an institution's own competency identifier — distinct from CASE/CTDL and from the existing external_id field.
  • Defines a completeness rule requiring code for every tag in a Competency Taxonomy, enforced through a generic property on Taxonomy that only a Competency Taxonomy subclass turns on, so openedx_tagging never has to know what a Competency Taxonomy is.
  • Specifies assignment behavior: user-entered when given; a placeholder is generated only when a new tag is created with no code (via import or the existing Taxonomy Editing UI, which has no field for it yet); re-import can correct a placeholder but never clears or regenerates an existing value just because a file's column is blank.
  • Answers openedx/openedx-core#625 ("Determine what and how many ID fields should be added to the Taxonomy data model").

Why

Competency Management UX designs show a "Competency ID" that institutions click to build competency-criteria associations. Institutions need their own internal identifier for this, separate from any future CASE/CTDL identifiers. The existing external_id field can't be reused: it's de facto immutable and is the load-bearing lookup key for import/export matching, so giving it a second, editable meaning would break that contract.

Status

Proposed. This is a recommendation for engineering and Open edX community review, not an implementation — no code changes are included in this PR.

Dependencies and follow-ups (tracked separately, not in this PR)

  • #613 (implements the CompetencyTaxonomy model) needs to override the tag_code_required property this ADR defines. This ADR's property should land before or alongside [BE] Implement CBE core data models (CompetencyTaxonomy, criteria, learner status) #613, not after it — [BE] Implement CBE core data models (CompetencyTaxonomy, criteria, learner status) #613 depends on this ADR, not the reverse.
  • #634 ("Taxonomy subclasses: embrace or deprecate?") is an open architecture discussion that could affect the subclass-override mechanism this ADR relies on.
  • A pre-existing bug in the import validation path (a taxonomy-type cast gets discarded) needs its own fix for this ADR's completeness rule to work correctly.
  • Implementation tickets for the data model change, GET endpoint exposure, import/export support, Competency Management UI display, and docs/release notes aren't filed yet.

These are called out in a comment on #625 rather than in the ADR itself.

Proposes a generic Tag.code field in openedx_tagging to hold an
institution's own competency identifier, plus a completeness rule
that requires it for Competency Taxonomy tags without teaching
openedx_tagging what a Competency Taxonomy is. Answers issue openedx#625.
@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @mgwozdz-unicon!

This repository is currently maintained by @axim-engineering.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@ormsbee ormsbee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My overall question is around external_id, whether it's really such a bad thing for it to become mutable, and why that is. And what would external_id be set to vs. code should this ADR pass?

Comment on lines +17 to +18
short, human-readable identifier an institution can reference in its own systems
(spreadsheets, internal catalogs, etc). Clicking it drives the workflow that

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it always one identifier, or do institutions sometimes have multiple identifiers for the same tag (e.g. from different systems)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be only one identifier. Today Competency Frameworks/Taxonomies are sourced through hand crafted spreadsheets rather than any sort of source system probably 99% of the time. I'm pretty sure none of the other LMSs support multiple identifiers for competencies. If there were other identifiers, they would most likely be the CASE GUID or CTDL identifier, which should be explicitly stored as such.

Comment on lines +22 to +25
This identifier is explicitly **not** a CASE or CTDL identifier. Those are external
standards bodies' IDs that may need their own storage in the future; conflating them
with an institution's internal code would make each harder to reason about
independently. This ADR is scoped to the internal, institution-defined code only.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if I agree with that. If we can have a robust system that maps namespaced identifiers to tags, I could imagine us using a system where internal namespaces are peers to standardized ones.

That being said, if and when that time comes, we can always migrate the existing data that we have to some new, more general schema. So I do support the idea that we're focusing strictly on internal codes for now, with the simpler model that implies.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When support is added for the CASE and CTDL identifiers, I can definitely envision wanting something akin to a tag_identifiers table where the columns of that table are effectively just tag_id, other_id_type, and other_id to support other_id_types of CASE and CTDL. The institution id (code) and/or external_id could potentially be stored in this table too if desired. I wasn't trying to preclude that kind of an idea with this.

@mgwozdz-unicon mgwozdz-unicon Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although, it might make sense to continue keeping the third party identifiers separate from the institution/openedx identifiers. I'm not sure I'd want to migrate those (external_id and code) into this tags_identifiers table since they'd already be functioning fine where they are. Really the only reason to add a new table at all would be out of a fear that the number of additional identifiers might get unweildy, and really even just the 2 additional (CASE and CTDL) may not constitute "unweildy".

Comment on lines +34 to +36
purely as the lookup key to find and update existing tags on re-import. Repurposing
it as an admin-editable Competency ID would conflict with that existing, load-bearing
contract.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand on why this is currently immutable, and why it must remain so? My understanding was that external_id was created for a similar use case to what's being proposed here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to jog my memory on this... I think it was because we needed an identifier that wasn't internal to Open edX (since sharing identifiers internal to the platform was deemed a security concern) that could be stable on import/export. Because otherwise, if you edit the external_id and re-import, then the platform has no way of knowing what you were editing. Whereas institutions need a mutable identifier that they can keep as human readable and in alignment with the current tag name that may be edited as well.

@ormsbee ormsbee Jul 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is it accurate to say that if we either exported the UUID, or provided a "previous_id" column in the import CSV format so that we could remap external_id entries, we wouldn't need this new code field?

@mgwozdz-unicon

Copy link
Copy Markdown
Contributor Author

@ormsbee Great questions - I'm happy to update my draft for clarity around them, but I want to make sure any follow-up you may have is resolved first, so just let me know if you have follow-up questions/thoughts or if you're ready for me to update it.

@kdmccormick

kdmccormick commented Jul 7, 2026

Copy link
Copy Markdown
Member

I'm struggling to understand the need for code given the identifiers that Tags already have:

  • id, an immutable instance-local identifier
  • uuid, an immutable cross-platform identifier
  • external_id, another immutable cross platform identifier
  • value, a human-readable, editable identifier

Could you link to the UX designs and/or provide a concrete example of what a given Tag might have for its value, code, and external_id?

@mgwozdz-unicon

Copy link
Copy Markdown
Contributor Author

@kdmccormick

I'm struggling to understand the need for code given the identifiers that Tags already have:

  • id, an immutable instance-local identifier
  • uuid, an immutable cross-platform identifier
  • external_id, another immutable cross platform identifier
  • value, a human-readable, editable identifier

Could you link to the UX designs and/or provide a concrete example of what a given Tag might have for its value, code, and external_id?

Yes, here is a screenshot from the UX designs:
image

So in this example, "CCRS-1.3" would be the code value for the tag that has the value "Inference". Here CCRS stands for "Career and College Readiness Skills" which is the name of the Taxonomy. If the institution changed their code (institution competency id) to "CCRS-1.4" and re-uploaded and it had instead been the external_id value, then the platform would have no way of knowing what tag is supposed to get updated to "CCRS-1.4". So in this example, when the institution first does their upload, code and external_id may have the same value, but code can change while external_id must always be the same. The id would be internal to the instance and uuid can't be used for this purpose because it's not human readable.

@kdmccormick

Copy link
Copy Markdown
Member

Thanks, this helps a lot.

If the institution changed their code (institution competency id) to "CCRS-1.4" and re-uploaded and it had instead been the external_id value, then the platform would have no way of knowing what tag is supposed to get updated to "CCRS-1.4".

Is changing the institutional competency id a known use case? They appear to me, visually, like the sort of code that an institution would define once and then make a point to keep stable.

@ormsbee

ormsbee commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@mgwozdz-unicon:

... I want to make sure any follow-up you may have is resolved first, so just let me know if you have follow-up questions/thoughts or if you're ready for me to update it.

My main line of questioning is just to see if we can somehow amend the import format so that we can still use the external_id field for this data. Otherwise, I worry that we're going to end up with the case that external_id will be the same as code the vast majority of the time and look like duplication to end users, and that it won't be clear how or why they differ at other times.

@jesperhodge

jesperhodge commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@mgwozdz-unicon @ormsbee:
Am I right that some of the pushback is because we are trying to add a field to taxonomy that is not only irrelevant to non-competency taxonomies but also is a synonym for external_id yet referring to something else?
I know we planned to not have a Competency table but just have one tag in a competency taxonomy be a competency. But I'm wondering if we shouldn't add a CompetencyTagData or CompetencyMetadata table or something like that. That would allow us to add additional data to each competency tag right in the CBE applet, leaving the tag table as-is. It would be more scalable, because possibly we could in the future have more cases of customizing a taxonomy tag vs a tagging tag, and we would keep a very clean separation of concerns.
The new field code or competency_code or something like that would go in the new table and not pollute the taxonomy model. I feel we would have a lot more flexibility in adding any fields we want without needing to worry about the taxonomy model itself.
(Similar to how we have a CompetencyTaxonomy table now that just adds additional info to a taxonomy, marking it as a competency hierarchy without needing to change the taxonomy model).

@kdmccormick

kdmccormick commented Jul 7, 2026

Copy link
Copy Markdown
Member

But I'm wondering if we shouldn't add a CompetencyTagData or CompetencyMetadata table or something like that. That would allow us to add additional data to each competency tag right in the CBE applet, leaving the tag table as-is. It would be more scalable, because possibly we could in the future have more cases of customizing a taxonomy tag vs a tagging tag, and we would keep a very clean separation of concerns.

I think this is a path forward if there ends up being further competency-specific tag metadata. But at the moment, I think we're both reluctant about proliferation of identifiers (whether CBE-specific or not) without clarity that the existing identifiers don't suffice.

@bradenmacdonald

Copy link
Copy Markdown
Contributor

This use case is exactly what the external_id field is already for. "External" means "External to Open edX", not "external to the institution".

Making it mutable is fine.

If the institution changed their code (institution competency id) to "CCRS-1.4" and re-uploaded and it had instead been the external_id value, then the platform would have no way of knowing what tag is supposed to get updated to "CCRS-1.4".

This is a problem whether you use code or external_id. But I would say generally institutions shouldn't be changing the main ID ("code" in this example) of a tag at all. If they do, there are workarounds though, if we make the field mutable.

@jesperhodge

jesperhodge commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@bradenmacdonald I just updated the old PR for external_ids right now so that it is up to date and can be looked at and perhaps merged: #491
(I tagged you as reviewer)
-> but the problem described in the ADR is real, as changing external_id breaks the import-export workflow.
Making external_id mutable conflicts with this.
@ormsbee 's idea to store previous external_ids is a good solution to this, making external_id mutability okay, though it takes some finessing to make this work well with exports. (I think it would need a good amount of effort.)
@mgwozdz-unicon it is true that external_id is there for this purpose of syncing to an external system. What I don't understand: why does it need to be mutable? Is there an external_id for the imported CSV that points to something different than the tag code you want to add? Otherwise, why isn't the tag code added as external_id before importing the CSV? Is there no workaround to keep it immutable?

@bradenmacdonald

bradenmacdonald commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@ormsbee 's idea to store previous external_ids is a good solution to this, making external_id mutability okay, though it takes some finessing to make this work well with exports.

Do we need to include it on exports? I would think that previous_external_ids could be a field that's only used on imports, in the rare case of changing tag IDs, and not something we need to store or export. The goal of that field is to trigger a RenameTag in the import plan rather than a DeleteTag + AddTag.

I don't think our taxonomy exports need to include history or "previous IDs"; they don't even include any taxonomy metadata at the moment. In any case, there is an official taxonomy published somewhere else, which likely includes a proper changelog (example), so we don't need to make a poor-man's version of that. Just let people use the official upstream dataset if they need the full picture and history etc.

@jesperhodge

Copy link
Copy Markdown
Contributor

I would think that previous_external_ids could be a field that's only used on imports, in the rare case of changing tag IDs, and not something we need to store or export. The goal of that field is to trigger a RenameTag in the import plan rather than a DeleteTag + AddTag.

That sounds right to me, I should have referred to the import and not the export. And the solution you put here does seem much simpler than I thought.

@ormsbee

ormsbee commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

I agree that we don't need to persist the previous_id data in the database, or the export, and that it would only be read on import.

We might want to eventually persist a changelog, since we already break it down into ImportAction primitives anyway (e.g. one row per import, with a JSON representation of the actions that were taken).

@ormsbee

ormsbee commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Misc. consideration for testing (does not need to be part of any ADR): One edge case would be an external_id swap, e.g. "A" -> "B" and "B" -> "A" in the same import. Renames might require a two step process, where all tags-to-be-renamed are first renamed to something random before being reset to their final state external_id values.

PR review and follow-up team discussion converged on making Tag.external_id
mutable via a new previous_id import column, rather than adding a separate
Tag.code field, to give institutions an editable competency identifier.
The decision no longer adds a Tag.code field, so the filename should
reflect the mutable external_id approach instead.
@mgwozdz-unicon

Copy link
Copy Markdown
Contributor Author

I've revised the ADR based on this discussion plus a follow-up team conversation. Summary: dropped the new Tag.code field entirely. Tag.external_id becomes the institution's editable competency identifier instead, with mutability scoped to a new previous_id import column for now (Taxonomy Editing UI and the generic tag API stay unchanged, that's future-phase work).

To the open questions:

@ormsbee — yes, exactly what you proposed. A previous_id column in the import file, matched against an existing tag's external_id, remaps that tag in place (updates its external_id to the new id) instead of deleting and recreating it. That removes the need for code entirely, since external_id is now allowed to change through that path.

@kdmccormick — renaming the identifier is a confirmed use case, not hypothetical: institutions do this when they revise a competency taxonomy (e.g. after a curriculum or accreditation-standard update). They renumber the existing tags in place rather than standing up a new taxonomy for the same competencies, so we need the rename to survive without losing each tag's associations (like its CompetencyCriteria links) or its import/export identity.

@jesperhodge — on why it needs to be mutable at all: the alternative is treating every identifier change as delete-and-recreate, which is exactly what loses those associations and the tag's identity across import/export. That's the "keep external_id strictly immutable" alternative in the revised ADR, and it's rejected for that reason. Also, to your and Braden's resolved point: previous_id is import-only, not persisted, and doesn't need to show up in exports.

@jesperhodge on CompetencyMetadata: not adding it at this time. There's no competency-specific tag metadata to justify a separate table right now, since this ADR's identifier need is fully handled by external_id. If a descriptive field (tag description, competency statement) is needed later, I'd want it directly on Tag as a generic field rather than gated behind a competency-specific table, since non-competency taxonomies could reasonably want descriptions too. Happy to revisit if a real competency-specific field shows up.

@mgwozdz-unicon mgwozdz-unicon requested a review from ormsbee July 7, 2026 22:31

@kdmccormick kdmccormick left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@bradenmacdonald bradenmacdonald merged commit 1bd84c3 into openedx:main Jul 8, 2026
7 checks passed
@github-project-automation github-project-automation Bot moved this from Ready for Review to Done in Contributions Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

open-source-contribution PR author is not from Axim or 2U

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants